﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using UBCon.Ribbon.Interfaces;
using UBCon.Ribbon.Models;

namespace UBCon.Ribbon
{
    public partial class RibbonStackPanel : Panel, IScrollInfo, IRibbonControl
    {
        #region Constructors

        public RibbonStackPanel()
        {
            _translate = new TranslateTransform();

            RenderTransform = this._translate;

        }
        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a boolean value which indicates whether panel is animated on scroll.
        /// </summary>
        [Bindable(true),
        Browsable(true),
        Category("Behavior"),
        ReadOnly(false)]
        public bool AnimatedScroll
        {
            get { return (bool)GetValue(AnimatedScrollProperty); }
            set { SetValue(AnimatedScrollProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AnimatedScroll.  This enables animation, styling, binding, etc...
        public static readonly System.Windows.DependencyProperty AnimatedScrollProperty =
            DependencyProperty.Register("AnimatedScroll", typeof(bool), typeof(RibbonStackPanel), 
            new PropertyMetadata(true));

        [Bindable(true),
        Browsable(true),
        Category("Behavior"),
        ReadOnly(false)]
        public double ScrollBy
        {
            get { return (double)GetValue(ScrollByProperty); }
            set { SetValue(ScrollByProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ScrollBy.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ScrollByProperty =
            DependencyProperty.Register("ScrollBy", typeof(double), typeof(RibbonStackPanel), new PropertyMetadata(10.0D));


        /// <summary>
        /// Gets or sets panel's orientation.
        /// </summary>
        [Bindable(true),
        Browsable(true),
        Category("Behavior"),
        ReadOnly(false)]
        public System.Windows.Controls.Orientation Orientation
        {
            get { return (System.Windows.Controls.Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Orientation.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(RibbonStackPanel), new 
                PropertyMetadata(Orientation.Vertical));

        [Bindable(true),
        Browsable(true),
        Category("Behavior"),
        ReadOnly(false)]
        public bool FitsToViewport
        {
            get { return (bool)GetValue(FitsToViewportProperty); }
            set { SetValue(FitsToViewportProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FitsToViewport.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FitsToViewportProperty =
            DependencyProperty.Register("FitsToViewport", typeof(bool), typeof(RibbonStackPanel), 
            new PropertyMetadata(false));

        /// <summary>
        /// 
        /// </summary>
        internal bool Measured
        {
            get { return (bool)GetValue(MeasuredProperty); }
        }

        // Using a DependencyProperty as the backing store for Measured.  This enables animation, styling, binding, etc...
        internal static readonly DependencyPropertyKey MeasuredPropertyKey =
            DependencyProperty.RegisterReadOnly("Measured", typeof(bool), typeof(RibbonStackPanel), new PropertyMetadata(false));
        internal static readonly DependencyProperty MeasuredProperty = MeasuredPropertyKey.DependencyProperty;

        internal int Interval
        {
            get { return (int)GetValue(IntervalProperty); }
            set { SetValue(IntervalProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Interval.  This enables animation, styling, binding, etc...
        internal static readonly DependencyProperty IntervalProperty =
            DependencyProperty.Register("Interval", typeof(int), typeof(RibbonStackPanel), new PropertyMetadata(0));


        /// <summary>
        /// Gets the value of the UBCon.Ribbon.RibbonStackPanel.RibbonTabItemModel attached property for the 
        /// given dependency object.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        internal static RibbonTabItemModel GetRibbonTabItemModel(DependencyObject obj)
        {
            return (RibbonTabItemModel)obj.GetValue(RibbonTabItemModelProperty);
        }

        /// <summary>
        /// Sets the value of the UBCon.Ribbon.RibbonStackPanel.RibbonTabItemModel attached property for the 
        /// given dependency object.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        internal static void SetRibbonTabItemModel(DependencyObject obj, RibbonTabItemModel value)
        {
            obj.SetValue(RibbonTabItemModelProperty, value);
        }

        /// <summary>
        /// Identifies UBCon.Ribbon.RibbonStackPanel.RibbonTabItemModel attached property.
        /// </summary>
        internal static readonly DependencyProperty RibbonTabItemModelProperty =
            DependencyProperty.RegisterAttached("RibbonTabItemModel", typeof(RibbonTabItemModel), typeof(RibbonStackPanel));


        /// <summary>
        /// Gets the value of the UBCon.Ribbon.RibbonStackPanel.ContextualTabItemsGroupModel attached property for the 
        /// given dependency object.
        /// </summary>
        /// <param name="obj"></param>
        internal static ContextualTabItemsGroupModel GetContextualTabItemsGroupModel(DependencyObject obj)
        {
            return (ContextualTabItemsGroupModel)obj.GetValue(ContextualTabItemsGroupModelProperty);
        }

        /// <summary>
        /// Sets the value of the UBCon.Ribbon.RibbonStackPanel.ContextualTabItemsGroupModel attached property for the 
        /// given dependency object.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        internal static void SetContextualTabItemsGroupModel(DependencyObject obj, ContextualTabItemsGroupModel value)
        {
            obj.SetValue(ContextualTabItemsGroupModelProperty, value);
            
        }

        /// <summary>
        /// Identifies UBCon.Ribbon.RibbonStackPanel.ContextualTabItemsGroupModel attached property.
        /// </summary>
        internal static readonly DependencyProperty ContextualTabItemsGroupModelProperty =
            DependencyProperty.RegisterAttached("ContextualTabItemsGroupModel", typeof(ContextualTabItemsGroupModel), typeof(RibbonStackPanel));

        
        #endregion

        #region Depenency Property Changed Callbacks
        
        #endregion

        #region Overrides
        
        /// <summary>
        /// Invalidates the measurement state (layout) for the element. (Inherited from UIElement.)
        /// </summary>
        /// <param name="availableSize">The available size that this element can give to child elements. 
        /// Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
        /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            double w = 0, mSize = 0;

            if (Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                foreach (UIElement item in InternalChildren)
                {
                    item.Measure(infiniteSize);

                    w += item.DesiredSize.Width + Interval;

                    mSize = Math.Max(mSize, item.DesiredSize.Height);
                }
                if (double.IsPositiveInfinity(availableSize.Width))
                {
                    measureSize.Width = w;
                    //size.Width = w;
                }
                else
                {
                    if (!FitsToViewport)
                    {
                        measureSize.Width = Math.Min(availableSize.Width, w); 
                        //size.Width = Math.Min(availableSize.Width, w);
                    }
                    else
                    {
                        if (availableSize.Width < w)
                        {
                            MinimizeItems(availableSize, w);
                        }
                        measureSize.Width = availableSize.Width;
                        //size.Width = availableSize.Width;
                    }
                }
                measureSize.Height = mSize;
                //size.Height = GetMaxHeight();
            }
            else
            {
                foreach (UIElement item in InternalChildren)
                {
                    item.Measure(infiniteSize);

                    w += item.DesiredSize.Height + Interval;
                    
                    mSize = Math.Max(mSize, item.DesiredSize.Width);
                }
                if (double.IsPositiveInfinity(availableSize.Height))
                {
                    measureSize.Height = w;
                    //size.Height = w;
                }
                else
                {
                    measureSize.Height = Math.Min(availableSize.Height, w);
                    //size.Height = Math.Min(availableSize.Height, w);
                }
                measureSize.Width = mSize;
            }
            if (ScrollOwner != null)
            {
                UpdateScrollInfo(measureSize);
            }
            return measureSize;

        }

        /// <summary>
        /// Invalidates the arrange state (layout) for the element. 
        /// After the invalidation, the element will have its layout updated, which will occur 
        /// asynchronously unless subsequently forced by UpdateLayout. (Inherited from UIElement.)
        /// </summary>
        /// <param name="finalSize">The finalSize size that this element can give to child elements. 
        /// Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
        /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0; int count=InternalChildren.Count;

            if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                foreach (UIElement item in InternalChildren)
                {
                    arrangeRect.X = x;
                    arrangeRect.Y = 0;
                    arrangeRect.Width = item.DesiredSize.Width;
                    arrangeRect.Height = finalSize.Height;

                    item.Arrange(arrangeRect);
                   
                    x += item.DesiredSize.Width + Interval;
                }
            }
            else
            {
                foreach (UIElement item in InternalChildren)
                {
                    arrangeRect.X = 0;
                    arrangeRect.Y = x;
                    arrangeRect.Width = finalSize.Width;
                    arrangeRect.Height = item.DesiredSize.Height;

                    item.Arrange(arrangeRect);

                    x += item.DesiredSize.Height + Interval;
                }
            }

            SetValue(RibbonStackPanel.MeasuredPropertyKey, true);
            
            return finalSize;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Minimizes RibbonGroup control when windows's size changes.
        /// </summary>
        /// <param name="avaliableSize">Type: System.Windows.Size. Panel's avaliable Size.</param>
        /// <param name="actualWidth">Gets or </param>
        private void MinimizeItems(Size avaliableSize, double actualWidth)
        {
            int index = InternalChildren.Count - 1; double current = 0;

            while (actualWidth > avaliableSize.Width && index >= 0)
            {
                UIElement e = this.InternalChildren[index];

                if (e is RibbonGroup)
                {
                    RibbonGroup group = e as RibbonGroup;

                    if (!group.Minimized)
                    {
                        if (group.CanMinimize)
                        {
                            actualWidth -= group.DesiredSize.Width;

                            current = group.DesiredSize.Width;

                            group.SetValue(RibbonGroup.MinimizedPropertyKey, true);

                            group.Measure(infiniteSize);

                            if (System.Math.Abs(current - group.DesiredSize.Width) <= 25)
                            {
                                actualWidth += current;

                                group.SetValue(RibbonGroup.CanMinimizePropertyKey, false);

                                continue;
                            }
                            else
                            {
                                actualWidth += group.DesiredSize.Width;
                            }
                        }
                    }
                }
                index--;
            }

        }

        /// <summary>
        /// Gets maximum height among 
        /// </summary>
        /// <returns>Type: System.Double</returns>
        private double GetMaxHeight()
        {
            double max = 0;

            foreach (UIElement item in InternalChildren)
            {
                max = Math.Max(item.DesiredSize.Height, max);

            }
            return max;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private double GetMaxWidth()
        {
            double max = 0;
            foreach (System.Windows.UIElement item in InternalChildren)
            {
                max = Math.Max(item.DesiredSize.Width, max);

            }
            return max;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="avaliableSize"></param>
        /// <param name="actualWidth"></param>
        private void MaximizeObjects(Size avaliableSize, double actualWidth)
        {
            int index = 0; double current = 0;
            while (actualWidth < avaliableSize.Width && index <= this.InternalChildren.Count - 1)
            {
                System.Windows.UIElement e = this.InternalChildren[index];

                if (e is RibbonGroup)
                {
                    RibbonGroup group = e as RibbonGroup;

                    if (group.Minimized)
                    {
                        actualWidth -= group.DesiredSize.Width;
                        current = group.DesiredSize.Width;
                        group.SetValue(RibbonGroup.MinimizedPropertyKey, false);
                        group.Measure(infiniteSize);
                        actualWidth += group.DesiredSize.Width;
                    }
                }
                index++;

            }

        }
        #endregion

        #region Event Handlers

        /// <summary>
        /// Invoked whenever ScrollViewer's size changes.
        /// </summary>
        /// <param name="sender">Owner of the event.</param>
        /// <param name="e">Event data for the event.</param>
        private void ScrollOwner_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (e.WidthChanged)
            {
                if (this._extent.Width < this._viewport.Width)
                {
                    MaximizeObjects(this._viewport, this._extent.Width);
                }

            }
        }

        #endregion

        #region Fields
        internal TranslateTransform _translate;
        private Size _extent = new Size(0, 0), _viewport = new Size(0, 0), 
            infiniteSize=new Size(double.PositiveInfinity, double.PositiveInfinity),
            measureSize=new Size();
        private Point emptyPoint = new Point(0,0), offset = new Point(0, 0);
        private Rect arrangeRect = new Rect();

        private ScrollViewer scrollViewer;
        private bool canHScroll = false, canVScroll = false;
        internal List<RibbonTabItemModel> TabItemModels = new List<RibbonTabItemModel>();
        
        #endregion

        #region IRibbonControl Implementation

        /// <summary>
        /// Gets a boolean value indicating whether the contol's size is large
        /// </summary>
        public bool IsLarge
        {
            get { return true; }
        }

        #endregion
    }
}
